﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/3/9 11:23:10
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Exceptions;

namespace ImageK.Java
{
    /// <summary>
    /// java wrapper
    /// </summary>
    public class Polygon:Shape
    {
        /**
         * The total number of points.  The value of {@code npoints}
         * represents the number of valid points in this {@code Polygon}
         * and might be less than the number of elements in
         * {@link #xpoints xpoints} or {@link #ypoints ypoints}.
         * This value can be NULL.
         *
         * @serial
         * @see #addPoint(int, int)
         * @since 1.0
         */
        public int npoints;

        /**
         * The array of X coordinates.  The number of elements in
         * this array might be more than the number of X coordinates
         * in this {@code Polygon}.  The extra elements allow new points
         * to be added to this {@code Polygon} without re-creating this
         * array.  The value of {@link #npoints npoints} is equal to the
         * number of valid points in this {@code Polygon}.
         *
         * @serial
         * @see #addPoint(int, int)
         * @since 1.0
         */
        public int[] xpoints;

        /**
         * The array of Y coordinates.  The number of elements in
         * this array might be more than the number of Y coordinates
         * in this {@code Polygon}.  The extra elements allow new points
         * to be added to this {@code Polygon} without re-creating this
         * array.  The value of {@code npoints} is equal to the
         * number of valid points in this {@code Polygon}.
         *
         * @serial
         * @see #addPoint(int, int)
         * @since 1.0
         */
        public int[] ypoints;

        /**
         * The bounds of this {@code Polygon}.
         * This value can be null.
         *
         * @serial
         * @see #getBoundingBox()
         * @see #getBounds()
         * @since 1.0
         */
        protected Rectangle bounds;

        /*
         * JDK 1.1 serialVersionUID
         */
        private const long serialVersionUID = -6460061437900069969L;

        /*
         * Default length for xpoints and ypoints.
         */
        private const int MIN_LENGTH = 4;

        /**
         * Creates an empty polygon.
         * @since 1.0
         */
        public Polygon()
        {
            xpoints = new int[MIN_LENGTH];
            ypoints = new int[MIN_LENGTH];
        }

        /**
         * Constructs and initializes a {@code Polygon} from the specified
         * parameters.
         * @param xpoints an array of X coordinates
         * @param ypoints an array of Y coordinates
         * @param npoints the total number of points in the
         *                          {@code Polygon}
         * @exception  NegativeArraySizeException if the value of
         *                       {@code npoints} is negative.
         * @exception  IndexOutOfBoundsException if {@code npoints} is
         *             greater than the length of {@code xpoints}
         *             or the length of {@code ypoints}.
         * @exception  NullPointerException if {@code xpoints} or
         *             {@code ypoints} is {@code null}.
         * @since 1.0
         */
        public Polygon(int[] xpoints, int[] ypoints, int npoints)
        {
            // Fix 4489009: should throw IndexOutOfBoundsException instead
            // of OutOfMemoryError if npoints is huge and > {x,y}points.length
            if (npoints > xpoints.Length || npoints > ypoints.Length)
            {
                throw new IndexOutOfRangeException("npoints > xpoints.length || " +
                                                    "npoints > ypoints.length");
            }
            // Fix 6191114: should throw NegativeArraySizeException with
            // negative npoints
            if (npoints < 0)
            {
                throw new ArgumentException("npoints < 0");
            }
            // Fix 6343431: Applet compatibility problems if arrays are not
            // exactly npoints in length
            this.npoints = npoints;
            this.xpoints = new int[npoints];
            Array.Copy(xpoints, this.xpoints, npoints);
            this.ypoints = new int[npoints];
            Array.Copy(ypoints, this.ypoints, npoints);
        }

        /**
         * Resets this {@code Polygon} object to an empty polygon.
         * The coordinate arrays and the data in them are left untouched
         * but the number of points is reset to zero to mark the old
         * vertex data as invalid and to start accumulating new vertex
         * data at the beginning.
         * All internally-cached data relating to the old vertices
         * are discarded.
         * Note that since the coordinate arrays from before the reset
         * are reused, creating a new empty {@code Polygon} might
         * be more memory efficient than resetting the current one if
         * the number of vertices in the new polygon data is significantly
         * smaller than the number of vertices in the data from before the
         * reset.
         * @see         java.awt.Polygon#invalidate
         * @since 1.4
         */
        public void reset()
        {
            npoints = 0;
            bounds = Rectangle.Empty;
        }

        /**
         * Invalidates or flushes any internally-cached data that depends
         * on the vertex coordinates of this {@code Polygon}.
         * This method should be called after any direct manipulation
         * of the coordinates in the {@code xpoints} or
         * {@code ypoints} arrays to avoid inconsistent results
         * from methods such as {@code getBounds} or {@code contains}
         * that might cache data from earlier computations relating to
         * the vertex coordinates.
         * @see         java.awt.Polygon#getBounds
         * @since 1.4
         */
        public void invalidate()
        {
            bounds = Rectangle.Empty;
        }

        /**
         * Translates the vertices of the {@code Polygon} by
         * {@code deltaX} along the x axis and by
         * {@code deltaY} along the y axis.
         * @param deltaX the amount to translate along the X axis
         * @param deltaY the amount to translate along the Y axis
         * @since 1.1
         */
        public void translate(int deltaX, int deltaY)
        {
            for (int i = 0; i < npoints; i++)
            {
                xpoints[i] += deltaX;
                ypoints[i] += deltaY;
            }
            if (bounds != null)
            {
                bounds.translate(deltaX, deltaY);
            }
        }

        /*
         * Calculates the bounding box of the points passed to the constructor.
         * Sets {@code bounds} to the result.
         * @param xpoints[] array of <i>x</i> coordinates
         * @param ypoints[] array of <i>y</i> coordinates
         * @param npoints the total number of points
         */
        void calculateBounds(int[] xpoints, int[] ypoints, int npoints)
        {
            int boundsMinX = int.MaxValue;
            int boundsMinY = int.MaxValue;
            int boundsMaxX = int.MinValue;
            int boundsMaxY = int.MinValue;

            for (int i = 0; i < npoints; i++)
            {
                int x = xpoints[i];
                boundsMinX = Math.Min(boundsMinX, x);
                boundsMaxX = Math.Max(boundsMaxX, x);
                int y = ypoints[i];
                boundsMinY = Math.Min(boundsMinY, y);
                boundsMaxY = Math.Max(boundsMaxY, y);
            }
            bounds = new Rectangle(boundsMinX, boundsMinY,
                boundsMaxX - boundsMinX,
                boundsMaxY - boundsMinY);
        }

        /*
         * Resizes the bounding box to accommodate the specified coordinates.
         * @param x,&nbsp;y the specified coordinates
         */
        void updateBounds(int x, int y)
        {
            if (x < bounds.X)
            {
                bounds.Width = bounds.Width + (bounds.X - x);
                bounds.X = x;
            }
            else
            {
                bounds.Width = Math.Max(bounds.Width, x - bounds.X);
                // bounds.x = bounds.x;
            }

            if (y < bounds.Y)
            {
                bounds.Height = bounds.Height + (bounds.Y - y);
                bounds.Y = y;
            }
            else
            {
                bounds.Height = Math.Max(bounds.Height, y - bounds.Y);
                // bounds.y = bounds.y;
            }
        }

        /**
         * Appends the specified coordinates to this {@code Polygon}.
         * <p>
         * If an operation that calculates the bounding box of this
         * {@code Polygon} has already been performed, such as
         * {@code getBounds} or {@code contains}, then this
         * method updates the bounding box.
         * @param       x the specified X coordinate
         * @param       y the specified Y coordinate
         * @see         java.awt.Polygon#getBounds
         * @see         java.awt.Polygon#contains
         * @since 1.0
         */
        public void addPoint(int x, int y)
        {
            if (npoints >= xpoints.Length || npoints >= ypoints.Length)
            {
                int newLength = npoints * 2;
                // Make sure that newLength will be greater than MIN_LENGTH and
                // aligned to the power of 2
                if (newLength < MIN_LENGTH)
                {
                    newLength = MIN_LENGTH;
                }
                else if ((newLength & (newLength - 1)) != 0)
                {
                    newLength = IntegerUtils.HighestOneBit(newLength);
                }

                xpoints = Arrays.copyOf(xpoints, newLength);
                ypoints = Arrays.copyOf(ypoints, newLength);
            }
            xpoints[npoints] = x;
            ypoints[npoints] = y;
            npoints++;
            if (bounds != null)
            {
                updateBounds(x, y);
            }
        }

        /**
         * Gets the bounding box of this {@code Polygon}.
         * The bounding box is the smallest {@link Rectangle} whose
         * sides are parallel to the x and y axes of the
         * coordinate space, and can completely contain the {@code Polygon}.
         * @return a {@code Rectangle} that defines the bounds of this
         * {@code Polygon}.
         * @since 1.1
         */
        public Rectangle getBounds()
        {
            return getBoundingBox();
        }

        /**
         * Returns the bounds of this {@code Polygon}.
         * @return the bounds of this {@code Polygon}.
         * @deprecated As of JDK version 1.1,
         * replaced by {@code getBounds()}.
         * @since 1.0
         */
        public Rectangle getBoundingBox()
        {
            if (npoints == 0)
            {
                return new Rectangle();
            }
            if (bounds == Rectangle.Empty)
            {
                calculateBounds(xpoints, ypoints, npoints);
            }
            return bounds;
        }

        /**
         * Determines whether the specified {@link Point} is inside this
         * {@code Polygon}.
         * @param p the specified {@code Point} to be tested
         * @return {@code true} if the {@code Polygon} contains the
         *                  {@code Point}; {@code false} otherwise.
         * @see #contains(double, double)
         * @since 1.0
         */
        public bool contains(Point p)
        {
            return contains(p.X, p.Y);
        }

        /**
         * Determines whether the specified coordinates are inside this
         * {@code Polygon}.
         *
         * @param x the specified X coordinate to be tested
         * @param y the specified Y coordinate to be tested
         * @return {@code true} if this {@code Polygon} contains
         *         the specified coordinates {@code (x,y)};
         *         {@code false} otherwise.
         * @see #contains(double, double)
         * @since 1.1
         */
        public bool contains(int x, int y)
        {
            return contains((double)x, (double)y);
        }


        /**
         * Determines whether the specified coordinates are contained in this
         * {@code Polygon}.
         * @param x the specified X coordinate to be tested
         * @param y the specified Y coordinate to be tested
         * @return {@code true} if this {@code Polygon} contains
         *         the specified coordinates {@code (x,y)};
         *         {@code false} otherwise.
         * @see #contains(double, double)
         * @deprecated As of JDK version 1.1,
         * replaced by {@code contains(int, int)}.
         * @since 1.0
         */
        [Obsolete]
        public bool inside(int x, int y)
        {
            return contains((double)x, (double)y);
        }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public RectangleF getBounds2D()
        {
            var b = getBounds();
            return new RectangleF(b.X, b.Y, b.Width, b.Height);
        }


        public bool contains(double x, double y)
        {
            if (npoints <= 2 || !getBoundingBox().Contains(new Point((int)x, (int)y)))
            {
                return false;
            }
            int hits = 0;

            int lastx = xpoints[npoints - 1];
            int lasty = ypoints[npoints - 1];
            int curx, cury;

            // Walk the edges of the polygon
            for (int i = 0; i < npoints; lastx = curx, lasty = cury, i++)
            {
                curx = xpoints[i];
                cury = ypoints[i];

                if (cury == lasty)
                {
                    continue;
                }

                int leftx;
                if (curx < lastx)
                {
                    if (x >= lastx)
                    {
                        continue;
                    }
                    leftx = curx;
                }
                else
                {
                    if (x >= curx)
                    {
                        continue;
                    }
                    leftx = lastx;
                }

                double test1, test2;
                if (cury < lasty)
                {
                    if (y < cury || y >= lasty)
                    {
                        continue;
                    }
                    if (x < leftx)
                    {
                        hits++;
                        continue;
                    }
                    test1 = x - curx;
                    test2 = y - cury;
                }
                else
                {
                    if (y < lasty || y >= cury)
                    {
                        continue;
                    }
                    if (x < leftx)
                    {
                        hits++;
                        continue;
                    }
                    test1 = x - lastx;
                    test2 = y - lasty;
                }

                if (test1 < (test2 / (lasty - cury) * (lastx - curx)))
                {
                    hits++;
                }
            }

            return ((hits & 1) != 0);
        }

        // private Crossings getCrossings(double xlo, double ylo,
        //     double xhi, double yhi)
        // {
        //     Crossings cross = new Crossings.EvenOdd(xlo, ylo, xhi, yhi);
        //     int lastx = xpoints[npoints - 1];
        //     int lasty = ypoints[npoints - 1];
        //     int curx, cury;
        //
        //     // Walk the edges of the polygon
        //     for (int i = 0; i < npoints; i++)
        //     {
        //         curx = xpoints[i];
        //         cury = ypoints[i];
        //         if (cross.accumulateLine(lastx, lasty, curx, cury))
        //         {
        //             return null;
        //         }
        //         lastx = curx;
        //         lasty = cury;
        //     }
        //
        //     return cross;
        // }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public bool contains(PointF p)
        {
            return contains(p.X, p.Y);
        }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public bool intersects(double x, double y, double w, double h)
        {
            throw new NotImplementedException();
            // if (npoints <= 0 || !getBoundingBox().intersects(x, y, w, h))
            // {
            //     return false;
            // }
            //
            // Crossings cross = getCrossings(x, y, x+w, y+h);
            // return (cross == null || !cross.isEmpty());
        }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public bool intersects(Rectangle2D r)
        {
            return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public bool contains(double x, double y, double w, double h)
        {
            throw new NotImplementedException();
            // if (npoints <= 0 || !getBoundingBox().intersects(x, y, w, h))
            // {
            //     return false;
            // }
            //
            // Crossings cross = getCrossings(x, y, x+w, y+h);
            // return (cross != null && cross.covers(y, y+h));
        }

        /**
         * {@inheritDoc}
         * @since 1.2
         */
        public bool contains(Rectangle2D r)
        {
            return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
        }

    }
}
